package com.lost.octopus.es.helper.service;

import com.lost.octopus.es.helper.entity.model.QueryBuilder;
import com.lost.octopus.es.helper.entity.model.SortField;
import com.lost.octopus.es.helper.entity.query.PageIn;
import com.lost.octopus.es.helper.entity.query.QueryResult;
import com.lost.octopus.es.helper.entity.query.aggregation.AggregationStrategy;
import com.lost.octopus.es.helper.entity.query.aggregation.EsAggregations;
import com.lost.octopus.es.helper.entity.query.aggregation.NotAggregationStrategy;
import com.lost.octopus.es.helper.exception.NoEsDocumentAnnotationException;
import com.lost.octopus.es.helper.annotation.EsDocument;
import com.lost.octopus.es.helper.annotation.EsSort;
import com.lost.octopus.es.helper.exception.NoIndexNameException;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;

import org.apache.logging.log4j.util.Strings;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;

/**
 * es查询-解析{@link EsDocument}引基础实现
 * @author zhangbh
 **/
@Log4j2
public class DocumentEsServiceImpl extends SimpleEsServiceImpl implements DocumentEsService {

    @Override
    public int deleteIndex(Class<?> clazz) {
        return forEachRunnerByClazz(this::deleteIndex, clazz);
    }

    @Override
    public boolean reCreateIndex(Class<?> clazz) {
        deleteIndex(clazz);
        createWithMapping(clazz);
        return true;
    }

    @Override
    public int setMaxResultWindow(@NonNull Class<?> clazz, Integer maxResult) {
        return forEachRunnerByClazz(index -> setMaxResultWindow(index, maxResult), clazz);
    }

    @Override
    public int indexSetting(@NonNull Class<?> clazz, @NonNull Map<String, Object> settings) {
        return forEachRunnerByClazz(index -> indexSetting(index, settings), clazz);
    }

    @Override
    public <T> QueryResult<T> boolSearch(QueryBuilder search, Class<T> clazz) throws IOException {
        return boolSearch(search, clazz, getIndexes(search.getData()));
    }

    @Override
    public QueryResult<Map<String, Object>> boolSearch(QueryBuilder search) throws IOException {
        return boolSearch(search, getIndexes(search.getData()));
    }

    @Override
    public Aggregations boolSearchAgg(QueryBuilder search) throws IOException {
        // 分组聚合条件无需设置分页参数
        search.setPageIn(PageIn.of(1, 1));
        SearchResponse searchResponse = boolSearchGetResponse(search);
        return searchResponse.getAggregations();
    }

    @Override
    public SearchResponse boolSearchGetResponse(QueryBuilder search) throws IOException {
        return boolSearchGetResponse(search, getIndexes(search.getData()));
    }

    /**
     * 获取解析对象注解后的查询对象,如果想自定义扩展可以使用此方法
     * @param query 查询对象,不为空，需要有注解 {@link EsDocument}
     * @return 查询包装类
     */
    protected BoolQueryBuilder getBoolQueryBuilder(@NonNull Object query) {
        return addSearchParams(QueryBuilders.boolQuery(), query, getIndexes(query));
    }

    /**
     * 获取索引数组
     * @param object 类型，需要有注解 {@link EsDocument}
     * @return 索引数组
     */
    protected String[] getIndexes(Object object) {
        Class<?> clazz;
        if (object instanceof Class){
            clazz = (Class<?>) object;
            log.info("当前object类型已经是class类型:{}",clazz.getSimpleName());
        }else {
            clazz = object.getClass();
            log.info("当前object类型非class类型，转换为class类型:{}",clazz.getSimpleName());
        }
        EsDocument annotation = getDocumentAnno(clazz);
        String[] indexName = null;
        String customIndexName = annotation.customIndexName();
        String realCustomIndexName = Strings.EMPTY;
        try {
            Field customIndexNameField = clazz.getDeclaredField("customIndexName");
            customIndexNameField.setAccessible(true);
            realCustomIndexName = (String) customIndexNameField.get(object);
        }catch (Exception e){
            log.info("{}类中未找到自定义索引customIndexName字段", clazz.getName());
        }
        if (!StringUtils.hasText(customIndexName) || !StringUtils.hasText(realCustomIndexName)) {
            log.info("{}类中无自定义索引名称或者注解和属性没有同时存在customIndexName，获取注解中indexName索引名称", clazz.getName());
            indexName = annotation.indexName();
        }else {
            indexName = new String[]{realCustomIndexName};
        }
        if (indexName.length == 0) {
            throw new NoIndexNameException(clazz.getName());
        }
        // 获取真实的index名称，防止采取别名策略等情况
        for (int i = 0; i < indexName.length; i++) {
            indexName[i] = getRealIndexName(indexName[i]);
        }
        return indexName;
    }

    /**
     * 获取注解
     * @param clazz 类型
     * @return 注解信息
     */
    protected EsDocument getDocumentAnno(Class<?> clazz) {
        if (clazz == null) {
            throw new NullPointerException("无法解析类型,类型为空");
        }
        EsDocument annotation = clazz.getAnnotation(EsDocument.class);
        if (annotation == null) {
            throw new NoEsDocumentAnnotationException(clazz.getName());
        }
        return annotation;
    }

    @Override
    protected void addAggregation(SearchSourceBuilder sourceBuilder, Object query) {
        // 先看有无自定义条件，有则增加
        EsDocument annotation = query.getClass().getAnnotation(EsDocument.class);
        Class<? extends AggregationStrategy> strategy = annotation.aggregationStrategy();

        if (strategy == null || strategy == NotAggregationStrategy.class) {
            super.addAggregation(sourceBuilder, query);
        } else {
            // EsDocument 中有自定义分组条件，使用
            EsAggregations esAggregations;
            try {
                AggregationStrategy aggregationStrategy = strategy.newInstance();
                // 根据自定义的getter对象，获取聚合构建器
                esAggregations = aggregationStrategy.use(query);
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException("聚合配置获取失败", e);
            }
            // 获取的分组策略加入查询
            addAggregationToBuilder(sourceBuilder, esAggregations);
        }
    }

    /**
     * 获取排序条件
     * @param query 请求对象
     * @return 请求构造器
     */
    @Override
    protected List<FieldSortBuilder> getSorts(Object query) {
        // 注解，包含定义的排序信息
        EsDocument document = getDocumentAnno(query.getClass());
        // 循环确定有sort的注解字段
        if (document == null) {
            return Collections.emptyList();
        }
        // 循环先根据order排序好
        List<SortField> sortList = new ArrayList<>();
        EsSort[] sorts = document.sorts();
        Arrays.stream(sorts).forEach(sort -> {
            boolean addSort = isAddSort(query, sort);
            if (!addSort) {
                return;
            }
            sortList.add(new SortField(sort.name(), sort, sort.order()));
        });
        // 如果没有查询到注解，则实用super方式检查排序
        if (CollectionUtils.isEmpty(sortList)) {
            return super.getSorts(query);
        }
        return getFieldSort(sortList);
    }

    /**
     * 解析clazz中的 EsDocument.indexId调用方法
     * @param func 执行方法
     * @param clazz 类型，需要有注解 {@link EsDocument}
     * @return 执行总成功数
     */
    protected int forEachRunnerByClazz(Predicate<String> func, Class<?> clazz) {
        String[] indexs = getIndexes(clazz);
        AtomicInteger count = new AtomicInteger(0);
        Arrays.stream(indexs).forEach(index -> {
            boolean success = func.test(index);
            count.addAndGet(success ? 1 : 0);
        });
        return count.get();
    }

}
